﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.Text;

using Kenly.DBFramework.Reflection;
using Kenly.DBFramework;

namespace Kenly.DBFramework.Gateway
{
    /// <summary>
    /// The gateway to a database view.
    /// </summary>
    [Serializable]
    public abstract partial class ViewGateway<T> : IViewGateway where T : ViewGateway<T>, new()
    {
        private T _entity;
        private static Order _order = new Order();
        private static string _commandText;

        #region Properties


        /// <summary>
        /// Entity
        /// </summary>
        internal T Entity
        {
            get
            {
                return _entity;
            }
            set
            {
                _entity = value;
            }
        }

        /// <summary>
        /// The order to sort the result of a query.
        /// </summary>
        protected static Order SortedOrder
        {
            get
            {
                return _order;
            }
            set
            {
                _order = value;
            }
        }

        /// <summary>
        ///  Gets config from ConfigFactory.
        /// </summary>
        protected static GatewayConfig Config
        {
            get
            {
                return ConfigManager.FindConfig(typeof(T));
            }
        }

        /// <summary>
        /// CommandText
        /// </summary>
        internal static protected string CommandText
        {
            get
            {
                return _commandText;
            }
        }

        #endregion


        /// <summary>
        /// 
        /// </summary>
        protected ViewGateway()
        {
            this.Entity = (T)this;
        }


        #region Initialize


        //private static void SetupGateway(GatewayConfig config)
        //{
        //    if (config == null)
        //    {
        //        throw new ArgumentNullException("config", MsgResource.ConnectionStringMissing);
        //    }
        //    if (string.IsNullOrEmpty(config.ConnectionString))
        //    {
        //        throw new ConnectionException(MsgResource.ConnectionStringMissing);
        //    }

        //    _config = config;
        //}


        /// <summary>
        /// Specifies the DatabaseType and connectionString.
        /// </summary>
        /// <param name="dbType"></param>
        /// <param name="connectionString"></param>
        //[Obsolete("This method is abandoned.")]
        //protected static void InitializeGateway(DatabaseType dbType, string connectionString)
        //{
        //    GatewayConfig config = new GatewayConfig();

        //    config.DatabaseType = dbType;
        //    config.ConnectionString = connectionString;

        //    InitializeGateway(config);
        //}

        /// <summary>
        /// 
        /// </summary>
        /// <param name="config"></param>
        //[Obsolete("This method is abandoned.")]
        //protected static void InitializeGateway(GatewayConfig config)
        //{
        //    if (config == null)
        //    {
        //        throw new ArgumentNullException("config", MsgResource.ConnectionStringMissing);
        //    }
        //    if (string.IsNullOrEmpty(config.ConnectionString))
        //    {
        //        throw new ConnectionException(MsgResource.ConnectionStringMissing);
        //    }

        //    _config = config;
        //}

        /// <summary>
        /// InitializeInstance
        /// </summary>
        /// <param name="entity"></param>
        //[Obsolete("This method is abandoned.")]
        //protected void InitializeInstance(T entity)
        //{
            //if (entity == null)
            //{
            //    throw new GatewayException(MsgResource.EmptyEntity);
            //}
            //_entity = entity;
        //}

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        private static IViewCommandExecutor CreateCommandExecutor()
        {
            var currentConfig = Config;

            IDatabase database = DatabaseFactory.CreateInstance(currentConfig.DatabaseType);
            database.ConnectionString = currentConfig.ConnectionString;
            database.Command.CommandTimeout = currentConfig.CommandTimeout;
            return database.CreateViewCommandExecutor();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        private static ICommandReflector CreateCommandReflector()
        {
            return CommandReflectorFactory.CreateInstance(Config.DatabaseType);
        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="config"></param>
        public virtual void RefreshConfig(GatewayConfig config)
        {
            if (config == null)
            {
                return;
            }

            ConfigManager.Configs[typeof(T)] = config;
        }



        #endregion


        #region Operations




        #region Basic

        /// <summary>
        /// Retrieves all association entities.
        /// </summary>
        public ArrayList RetrieveAssociations()
        {
            return AssociationReflection.InvokeFindMethods(this.Entity);
        }

        /// <summary>
        /// Retrieves the association entities which are of the specified types.
        /// </summary>
        /// <param name="elementTypes">The types of association entityies</param>
        /// <returns></returns>
        public ArrayList RetrieveAssociations(params Type[] elementTypes)
        {
            return AssociationReflection.InvokeFindMethods(this.Entity, elementTypes);
        }

        /// <summary>
        /// Retrieves the association entity which is of the specified type.
        /// </summary>
        /// <param name="elementType">The type of association entity</param>
        /// <returns></returns>
        public ArrayList RetrieveAssociations(Type elementType)
        {
            return AssociationReflection.InvokeFindMethods(this.Entity, elementType);
        }

        /// <summary>
        /// Retrieves the entity from database table by identity.
        /// </summary>
        /// <returns></returns>
        public void RetrieveById()
        {
            string sql = CreateCommandReflector().SelectById(this.Entity);
            RetrieveBySql(sql);
        }

        /// <summary>
        /// Retrieves the entity from database table by primary key.
        /// </summary>
        /// <returns></returns>
        public void RetrieveByPK()
        {
            string sql = CreateCommandReflector().SelectByPK(this.Entity);
            RetrieveBySql(sql);
        }

        /// <summary>
        /// Retrieves the entity from database table by specified sql statement.
        /// </summary>
        /// <param name="sql">specified sql statement</param>
        /// <returns></returns>
        internal protected virtual void RetrieveBySql(string sql)
        {
            _commandText = sql;
            DataTable dt = new DataTable();
            dt.Locale = CultureInfo.InvariantCulture;
            dt = CreateCommandExecutor().ExecuteDataTable(sql, false);
            //ClearOrder();
            if (dt != null && dt.Rows.Count > 0)
            {
                EntityMapper<T>.AdaptToEntity(dt);
                T entity = EntityMapper<T>.ToEntity(dt.Rows[0]);
                EntityUtility.ShadowCopy(this.Entity, entity);
            }
        }

        private static void ClearOrder()
        {
            if (_order != null)
            {
                _order.Clear();
            }
        }

        #endregion  //Basic


        #region Query


        internal static string ReflectSelectTop(string condition, int topCount, List<string> propertyNames)
        {
            return CreateCommandReflector().SelectTop(typeof(T), condition, topCount, _order, propertyNames);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="topCount"></param>
        /// <param name="propertyNames"></param>
        /// <returns></returns>
        protected static DataTable QueryTop(int topCount, params string[] propertyNames)
        {
            return QueryTop("1=1", topCount, propertyNames);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="condition"></param>
        /// <param name="topCount"></param>
        /// <param name="propertyNames"></param>
        /// <returns></returns>
        protected static DataTable QueryTop(string condition, int topCount, params string[] propertyNames)
        {
            List<string> propertyNameList = new List<string>();
            if (propertyNames != null)
            {
                propertyNameList.AddRange(propertyNames);
            }
            string sql = ReflectSelectTop(condition, topCount, propertyNameList);
            return QueryBySql(sql);
        }


        /// <summary>
        /// Queries database by specified condition and only selects the columns corresponding to the specified property names.
        /// </summary>
        /// <param name="condition"></param>
        /// <param name="propertyNames"></param>
        /// <returns></returns>
        protected static DataTable Query(string condition, params string[] propertyNames)
        {
            List<string> propertyNameList = new List<string>();
            if (propertyNames != null)
            {
                propertyNameList.AddRange(propertyNames);
            }
            string sql = CreateCommandReflector().SelectByCondition(typeof(T), condition, SortedOrder, propertyNameList);
            return QueryBySql(sql);
        }

        private static DataTable QueryBySql(string sql)
        {
            DataTable dt = new DataTable();
            dt.Locale = CultureInfo.InvariantCulture;
            dt = CreateCommandExecutor().ExecuteDataTable(sql, false);
            _commandText = sql;
            ClearOrder();
            EntityMapper<T>.AdaptToEntity(dt);
            return dt;
        }

        /// <summary>
        /// Queries all rows from the database table and only selects the columns corresponding to the specified property names.
        /// </summary>
        /// <param name="propertyNames"></param>
        /// <returns></returns>
        protected static DataTable QueryAll(params string[] propertyNames)
        {
            return Query(" 1=1 ", propertyNames);
        }



        internal static string ReflectSelectPaging(string condition, int pageSize, int pageIndex, List<string> propertyNames, bool sqlServer2000)
        {
            return sqlServer2000 ?
                SqlServerCommandReflector.SelectPaging2000(typeof(T), condition, SortedOrder, pageSize, pageIndex, propertyNames) :
                CreateCommandReflector().SelectPaging(typeof(T), condition, SortedOrder, pageSize, pageIndex, propertyNames);
        }


        /// <summary>
        /// Queries database by custom condition and only returns a page of rows with specified size.
        /// </summary>
        /// <param name="condition"></param>
        /// <param name="pagingArg"></param>
        /// <param name="sqlServer2000"></param>
        /// <param name="propertyNames"></param>
        /// <returns></returns>
        protected static DataTable Query(string condition, PagingArg pagingArg, bool sqlServer2000, params string[] propertyNames)
        {
            int pageSize = pagingArg.PageSize;
            int pageIndex = pagingArg.PageIndex;
            pagingArg.RowCount = Count(condition);

            pagingArg.PageCount = (pageSize == 0) ?
                0 : ((pagingArg.RowCount % pageSize)) == 0 ?
                (pagingArg.RowCount / pageSize) : (pagingArg.RowCount / pageSize + 1);

            List<string> propertyNameList = new List<string>();
            if (propertyNames != null)
            {
                propertyNameList.AddRange(propertyNames);
            }
            string sql = ReflectSelectPaging(condition, pageSize, pageIndex, propertyNameList, sqlServer2000);
            return QueryBySql(sql);
        }

        /// <summary>
        /// Queries database by custom condition and only returns a page of rows with specified size.
        /// </summary>
        /// <param name="condition"></param>
        /// <param name="pagingArg"></param>
        /// <param name="propertyNames"></param>
        /// <returns></returns>
        protected static DataTable Query(string condition, PagingArg pagingArg, params string[] propertyNames)
        {
            return Query(condition, pagingArg, false, propertyNames);
        }


        /// <summary>
        /// Queries all rows from the database table and only returns a page of rows with specified size. 
        /// </summary>
        /// <param name="pagingArg"></param>
        /// <param name="propertyNames"></param>
        /// <returns></returns>
        protected static DataTable Query(PagingArg pagingArg, params string[] propertyNames)
        {
            return Query(" 1=1 ", pagingArg, false, propertyNames);
        }

        /// <summary>
        /// Queries all rows from the database table and only returns a page of rows with specified size. 
        /// </summary>
        /// <param name="pagingArg"></param>
        /// <param name="sqlServer2000"></param>
        /// <param name="propertyNames"></param>
        /// <returns></returns>
        protected static DataTable Query(PagingArg pagingArg, bool sqlServer2000, params string[] propertyNames)
        {
            return Query(" 1=1 ", pagingArg, sqlServer2000, propertyNames);
        }


        #endregion  //Query


        #region Query For Late Bound


        private static T FindTopOneForLateBound(string condition)
        {
            DataTable dt = QueryTop(condition, 1, null);
            if (dt.Rows.Count > 0)
            {
                return EntityMapper<T>.ToEntity(dt.Rows[0]);
            }
            else
            {
                return ObjectBuilder.CreateInstance<T>();
            }
        }

        private static DataTable QueryManyForLateBound(string condition)
        {
            return QueryTop(condition, -1, null);
        }

        private static T[] FindManyForLateBound(string condition)
        {
            DataTable dt = QueryManyForLateBound(condition);
            return EntityMapper<T>.ToEntities(dt).ToArray();
        }

        private DataTable QueryManyByInterrelationForLateBound(Type interrelationType, string referenceCondition)
        {
            string sql = CreateCommandReflector().SelectByInterrelationTable(this.Entity, interrelationType, referenceCondition, _order, null);
            return QueryBySql(sql);
        }

        private T[] FindManyByInterrelationForLateBound(Type interrelationType, string referenceCondition)
        {
            DataTable dt = QueryManyByInterrelationForLateBound(interrelationType, referenceCondition);
            return EntityMapper<T>.ToEntities(dt).ToArray();
        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="columnName"></param>
        /// <returns></returns>
        protected static IList Distinct(string columnName)
        {
            return Distinct(columnName, " 1=1 ", OrderDirection.Ascend, true);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="columnName"></param>
        /// <param name="direction"></param>
        /// <returns></returns>
        protected static IList Distinct(string columnName, OrderDirection direction)
        {
            return Distinct(columnName, " 1=1 ", direction, true);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="columnName"></param>
        /// <param name="ignoreNull"></param>
        /// <returns></returns>
        protected static IList Distinct(string columnName, bool ignoreNull)
        {
            return Distinct(columnName, " 1=1 ", OrderDirection.Ascend, ignoreNull);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="columnName"></param>
        /// <param name="condition"></param>
        /// <returns></returns>
        protected static IList Distinct(string columnName, string condition)
        {
            return Distinct(columnName, condition, OrderDirection.Ascend, true);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="columnName"></param>
        /// <param name="condition"></param>
        /// <param name="ignoreNull"></param>
        /// <returns></returns>
        protected static IList Distinct(string columnName, string condition, bool ignoreNull)
        {
            return Distinct(columnName, condition, OrderDirection.Ascend, ignoreNull);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="columnName"></param>
        /// <param name="condition"></param>
        /// <param name="direction"></param>
        /// <returns></returns>
        protected static IList Distinct(string columnName, string condition, OrderDirection direction)
        {
            return Distinct(columnName, condition, direction, true);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="columnName"></param>
        /// <param name="condition"></param>
        /// <param name="direction"></param>
        /// <param name="ignoreNull"></param>
        /// <returns></returns>
        protected static IList Distinct(string columnName, string condition, OrderDirection direction, bool ignoreNull)
        {
            IList result = new ArrayList();
            if (string.IsNullOrEmpty(columnName))
            {
                return result;
            }

            DataTable dt = DistinctDataTable(columnName, condition, direction);

            if (dt.Columns.Count <= 0)
            {
                return result;
            }

            ConvertToList(dt, result, ignoreNull);

            return result;
        }

        private static void ConvertToList(DataTable dt, IList result, bool ignoreNull)
        {
            foreach (DataRow row in dt.Rows)
            {
                if (ignoreNull && IsNullData(row[0]))
                {
                    continue;
                }

                result.Add(row[0]);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="columnName"></param>
        /// <returns></returns>
        protected static List<TColumn> Distinct<TColumn>(string columnName)
        {
            return Distinct<TColumn>(columnName, " 1=1 ", OrderDirection.Ascend, true);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TColumn"></typeparam>
        /// <param name="columnName"></param>
        /// <param name="ignoreNull"></param>
        /// <returns></returns>
        protected static List<TColumn> Distinct<TColumn>(string columnName, bool ignoreNull)
        {
            return Distinct<TColumn>(columnName, " 1=1 ", OrderDirection.Ascend, ignoreNull);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TColumn"></typeparam>
        /// <param name="columnName"></param>
        /// <param name="direction"></param>
        /// <returns></returns>
        protected static List<TColumn> Distinct<TColumn>(string columnName, OrderDirection direction)
        {
            return Distinct<TColumn>(columnName, " 1=1 ", direction, true);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TColumn"></typeparam>
        /// <param name="columnName"></param>
        /// <param name="condition"></param>
        /// <returns></returns>
        protected static List<TColumn> Distinct<TColumn>(string columnName, string condition)
        {
            return Distinct<TColumn>(columnName, condition, OrderDirection.Ascend, true);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TColumn"></typeparam>
        /// <param name="columnName"></param>
        /// <param name="condition"></param>
        /// <param name="ignoreNull"></param>
        /// <returns></returns>
        protected static List<TColumn> Distinct<TColumn>(string columnName, string condition, bool ignoreNull)
        {
            return Distinct<TColumn>(columnName, condition, OrderDirection.Ascend, ignoreNull);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TColumn"></typeparam>
        /// <param name="columnName"></param>
        /// <param name="condition"></param>
        /// <param name="direction"></param>
        /// <returns></returns>
        protected static List<TColumn> Distinct<TColumn>(string columnName, string condition, OrderDirection direction)
        {
            return Distinct<TColumn>(columnName, condition, direction, true);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TColumn"></typeparam>
        /// <param name="columnName"></param>
        /// <param name="condition"></param>
        /// <param name="direction"></param>
        /// <param name="ignoreNull"></param>
        /// <returns></returns>
        protected static List<TColumn> Distinct<TColumn>(string columnName, string condition, OrderDirection direction, bool ignoreNull)
        {
            List<TColumn> result = new List<TColumn>();
            if (string.IsNullOrEmpty(columnName))
            {
                return result;
            }

            DataTable dt = DistinctDataTable(columnName, condition, direction);

            ConvertToList(dt, result, ignoreNull);

            return result;
        }

        private static DataTable DistinctDataTable(string columnName, string condition, OrderDirection direction)
        {
            string sql = CreateCommandReflector().SelectDistinctByCondition(typeof(T), columnName, condition, direction);
            _commandText = sql;

            DataTable dt = new DataTable();
            dt.Locale = CultureInfo.InvariantCulture;
            dt = CreateCommandExecutor().ExecuteDataTable(sql, false);

            ClearOrder();

            return dt;
        }

        private static void ConvertToList<TColumn>(DataTable dt, List<TColumn> result, bool ignoreNull)
        {
            if (dt.Columns.Count <= 0 || dt.Columns[0].DataType != typeof(TColumn))
            {
                return;
            }

            foreach (DataRow row in dt.Rows)
            {
                if (ignoreNull && IsNullData(row[0]))
                {
                    continue;
                }

                if (IsNullData(row[0]))
                {
                    result.Add(default(TColumn));
                    continue;
                }

                result.Add((TColumn)row[0]);
            }
        }

        private static bool IsNullData(object data)
        {
            return (data is DBNull) || data == null;
        }

        #endregion


        #region Find

        /// <summary>
        /// 
        /// </summary>
        /// <param name="propertyNames"></param>
        /// <returns></returns>
        protected static List<T> FindAll(params string[] propertyNames)
        {
            DataTable dt = QueryAll(propertyNames);
            return EntityMapper<T>.ToEntities(dt);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="condition"></param>
        /// <param name="propertyNames"></param>
        /// <returns></returns>
        protected static List<T> Find(string condition, params string[] propertyNames)
        {
            DataTable dt = Query(condition, propertyNames);
            return EntityMapper<T>.ToEntities(dt);
        }

        /// <summary>
        /// Queries database by custom condition and only returns a page of rows with specified size.
        /// </summary>
        /// <param name="condition"></param>
        /// <param name="pagingArg"></param>
        /// <param name="sqlServer2000"></param>
        /// <param name="propertyNames"></param>
        /// <returns></returns>
        protected static List<T> Find(string condition, PagingArg pagingArg, bool sqlServer2000, params string[] propertyNames)
        {
            DataTable dt = Query(condition, pagingArg, sqlServer2000, propertyNames);
            return EntityMapper<T>.ToEntities(dt);
        }

        /// <summary>
        /// Queries database by custom condition and only returns a page of rows with specified size.
        /// </summary>
        /// <param name="condition"></param>
        /// <param name="pagingArg"></param>
        /// <param name="propertyNames"></param>
        /// <returns></returns>
        protected static List<T> Find(string condition, PagingArg pagingArg, params string[] propertyNames)
        {
            return Find(condition, pagingArg, false, propertyNames);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="pagingArg"></param>
        /// <param name="sqlServer2000"></param>
        /// <param name="propertyNames"></param>
        /// <returns></returns>
        protected static List<T> Find(PagingArg pagingArg, bool sqlServer2000, params string[] propertyNames)
        {
            return Find(" 1=1 ", pagingArg, sqlServer2000, propertyNames);
        }

        /// <summary>
        /// Queries all rows from the database table and only returns a page of rows with specified size. 
        /// </summary>
        /// <param name="pagingArg"></param>
        /// <param name="propertyNames"></param>
        /// <returns></returns>
        protected static List<T> Find(PagingArg pagingArg, params string[] propertyNames)
        {
            return Find(" 1=1 ", pagingArg, false, propertyNames);
        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="condition"></param>
        /// <param name="topCount"></param>
        /// <param name="propertyNames"></param>
        /// <returns></returns>
        protected static List<T> FindTop(string condition, int topCount, params string[] propertyNames)
        {
            DataTable dt = QueryTop(condition, topCount, propertyNames);
            return EntityMapper<T>.ToEntities(dt);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="topCount"></param>
        /// <param name="propertyNames"></param>
        /// <returns></returns>
        protected static List<T> FindTop(int topCount, params string[] propertyNames)
        {
            return FindTop("1=1", topCount, propertyNames);
        }

        ///// <summary>
        ///// Retrieves the first entity by specified condition.
        ///// </summary>
        ///// <param name="condition"></param>
        ///// <returns></returns>
        //protected static T FindTop(string condition)
        //{
        //    T firstOne = ObjectBuilder.CreateInstance<T>();
        //    DataTable dt = QueryTop(condition, 1);
        //    GetFirstOne(dt, firstOne);
        //    return firstOne;
        //}

        /// <summary>
        /// Retrieves the first entity by specified condition.
        /// </summary>
        /// <param name="condition"></param>
        /// <param name="firstOne"></param>
        protected static void FindTop(string condition, T firstOne)
        {
            DataTable dt = QueryTop(condition, 1);
            GetFirstOne(dt, firstOne);
        }

        /// <summary>
        /// Retrieves the first entity.
        /// </summary>
        /// <param name="firstOne"></param>
        protected static void FindTop(T firstOne)
        {
            FindTop("1=1", firstOne);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="dt"></param>
        /// <param name="firstOne"></param>
        private static void GetFirstOne(DataTable dt, T firstOne)
        {
            if (dt != null && dt.Rows.Count > 0)
            {
                EntityMapper<T>.CopyToEntity(firstOne, dt.Rows[0]);
            }
        }


        #endregion //Find

        #endregion  //Operations

    }


}